/*
 * Copyright 2008 Wink Saville
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.saville.hmc;

import java.util.LinkedList;
import com.saville.debug.Log;
import com.saville.msgcomp.*;
import com.saville.mc.*;

public class HmcBase extends Mc implements IHmc {

    /*
     * Constructor
     * 
     * NOTE: Parent class must call Mc#init(name, looper, doSendCmdStarted)
     * before completeConstruction is called.
     */
    public HmcBase(String name, int numStates, int initialState) {
        mName = name;

        states = new IState[numStates];
        stateInfo = new StateInfo[numStates];
        this.initialState = initialState;
    }

    public void completeConstruction() {
        /* Create the stateInfo array */
        for (int i = 0; i < states.length; i++) {
            stateInfo[i] = new StateInfo();
            stateInfo[i].index = i;
            stateInfo[i].istate = states[i];
            stateInfo[i].parent = null;
            stateInfo[i].entering = 0;
        }

        /*
         * We'll update parent info on a second pass that way we don't assume
         * parents are defined before their children, although they probably
         * are.
         */
        for (int i = 0; i < states.length; i++) {
            int parent = states[i].getParent();
            if (parent >= 0) {
                stateInfo[i].parent = stateInfo[parent];
            }
        }

        /* Initialize stack to empty and goto the initial state */
        stateStackTop = -1;
        gotoState(initialState);

        /* Now that construction is complete, send MC_CMD_STARTED */
        sendCmdStarted();
    }

    public void transitionTo(int state) {
        destState = state;
    }

    public void noTransition() {
        destState = -1;
    }

    public void unhandledMsg(Msg msg) {
        if (msg.cmdIsRequest()) {
            /*
             * We must try to reply so say it's unknown
             */
            sendReplyMsg(msg, McConst.STATUS_UNKNOWN_CMD);
        } else {
            /*
             * We'll silently ignore non-request messages
             */
        }
    }

    /*
     * Probably move deferring HmcBase as this can be common
     */
    @Override
    public void processMsg(Msg msg) {
        Msg deferredMsg;

        processOneMsg(msg);

        /*
         * After processing this msg handle any ready deferred msgs.
         */
        while ((deferredMsg = mReadyDeferredMsgs.peek()) != null) {
            mReadyDeferredMsgs.remove();
            processOneMsg(deferredMsg);
        }
    }

    public void deferMsg(Msg msg) {
        deferredMsgs.addLast(msg);
    }

    public void moveDeferredToReadyDeferredMsgs() {
        Msg deferredMsg;

        while ((deferredMsg = deferredMsgs.peek()) != null) {
            deferredMsgs.remove();
            mReadyDeferredMsgs.addLast(deferredMsg);
        }
    }

    protected void processOneMsg(Msg msg) {
        if (enteringIndex >= 0) {
            /*
             * We have a new message and previously did a transition where we
             * are going to enter some states, so we need to call their enter
             * entry points.
             */
            for (int i = enteringIndex; i <= stateStackTop; i++) {
                stateStack[i].istate.enterState(msg);
                stateStack[i].entering += 1;
            }
            enteringIndex = -1;
        }

        /*
         * Do current state
         */
        int stackIndex = stateStackTop;
        IState cur = curState;
        while (!cur.doState(msg)) {
            /*
             * Not processed
             */
            stackIndex -= 1;
            if (stackIndex >= 0) {
                /*
                 * Get the parent state and have it process the msg
                 */
                cur = stateStack[stackIndex].istate;
            } else {
                /*
                 * No parents left so its unhandled
                 */
                unhandledMsg(msg);
                break;
            }
        }

        if (destState >= 0) {
            /*
             * Transition to a new state, we need to determine the states we'll
             * be entering.
             */
            int commonIndex = setupTempStateStackWithStatesToEnter(destState);

            /*
             * Always exit the current state and any others that are not common
             * with the destination.
             */
            do {
                stateStack[stateStackTop].istate.exitState(msg);
                stateStackTop -= 1;
            } while ((stateStackTop >= 0) && (stateStack[stateStackTop].index != commonIndex));

            /*
             * Move the states we're entering to the stateStack and set
             * enteringIndex to the first index in the stack we'll be entering
             * so we execute enterState
             */
            enteringIndex = moveTempStateStackToStateStack();
            destState = -1;

            moveDeferredToReadyDeferredMsgs();
        }
    }

    protected LinkedList<Msg> mReadyDeferredMsgs = new LinkedList<Msg>();

    protected int moveTempStateStackToStateStack() {
        /*
         * Move the tempStateStack to stateStack but reverse the order.
         */
        int startingIndex = stateStackTop + 1;
        int i = tempStateStackCount - 1;
        int j = startingIndex;
        while (i >= 0) {
            if (j < MAX_LENGTH_STACKS) {
                stateStack[j] = tempStateStack[i];
                stateStack[j].entering = 1;
            } else {
                // TODO: Throw error if we overflow?
                Log.print("HmcBase: pushTemp, stack overflow");
            }
            j += 1;
            i -= 1;
        }

        stateStackTop = j - 1;
        curState = stateStack[stateStackTop].istate;
        return startingIndex;
    }

    protected void gotoState(int state) {
        int i;
        int max = MAX_LENGTH_STACKS - (stateStackTop + 1);

        StateInfo cur = stateInfo[state];
        tempStateStack[0] = cur;
        for (i = 1; (cur.parent != null) && i < max; i++) {
            tempStateStack[i] = cur.parent;
            cur = cur.parent;
        }
        tempStateStackCount = i;

        enteringIndex = moveTempStateStackToStateStack();
    }

    protected int setupTempStateStackWithStatesToEnter(int destState) {
        /*
         * Search up the parent list of the destination state for an active
         * state. An active state is one whose entering is != 0.
         */
        StateInfo cur = stateInfo[destState];
        tempStateStack[0] = cur;
        cur = cur.parent;
        int i;
        for (i = 1; (i < MAX_LENGTH_STACKS) && (cur != null) && (cur.entering == 0); i++) {
            tempStateStack[i] = cur;
            cur = cur.parent;
        }
        tempStateStackCount = i;
        return cur == null ? 0 : cur.index;
    }

    protected class StateInfo {
        int index;
        IState istate;
        StateInfo parent;
        int entering;
    }

    protected IState states[];
    protected StateInfo stateInfo[];

    protected static final int MAX_LENGTH_STACKS = 20;
    protected int initialState = -1;
    protected int enteringIndex = -1;
    protected int destState = -1;

    /* Optimization, duplicate of top of stateStack */
    protected IState curState = null;

    protected int stateStackTop = -1;
    protected StateInfo stateStack[] = new StateInfo[MAX_LENGTH_STACKS];
    protected StateInfo tempStateStack[] = new StateInfo[MAX_LENGTH_STACKS];
    protected int tempStateStackCount = 0;
    protected LinkedList<Msg> deferredMsgs = new LinkedList<Msg>();
    protected LinkedList<Msg> readyDeferredMsgs = new LinkedList<Msg>();
}
